package aliyun

import (
	"context"
	"fmt"
	"sync"
	"time"

	sls "github.com/aliyun/aliyun-log-go-sdk"
	"github.com/aliyun/aliyun-log-go-sdk/producer"
	"github.com/gogf/gf/v2/encoding/gbase64"
	"github.com/gogf/gf/v2/encoding/gjson"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gtime"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/golang/protobuf/proto"
)

type aliyunSlsConfig struct {
	Project  string // projectName
	Store    string // logStoreName
	Topic    string
	Query    string // queryExp
	From     int64
	To       int64
	PageSize int64 // maxLineNum
	Reverse  bool  // 反向
}

func NewSls(ctx context.Context, slug string) *aliyunSls {
	var aliyunConfig *aliyunConfig
	err := g.Config().MustGet(ctx, "org.aliyun").Struct(&aliyunConfig)
	if err != nil {
		panic(err)
	}

	var config *aliyunSlsConfig
	err = g.Config().MustGet(ctx, fmt.Sprintf("org.aliyun.sls.%s", slug)).Struct(&config)
	if err != nil {
		panic(err)
	}

	config.From = gtime.Now().Add(-time.Hour).Timestamp() // 一小时内
	config.To = gtime.Now().Timestamp()
	config.PageSize = 20
	config.Reverse = true

	return &aliyunSls{
		ctx:  ctx,
		slug: slug,

		Config:       config,
		AliyunConfig: aliyunConfig,

		producers:     map[string]*producer.Producer{},
		producersLock: &sync.Mutex{},
	}
}

type aliyunSls struct {
	ctx  context.Context
	slug string

	Config       *aliyunSlsConfig
	AliyunConfig *aliyunConfig

	client        sls.ClientInterface
	producers     map[string]*producer.Producer
	producersLock *sync.Mutex
}

func (r *aliyunSls) Project(project string) *aliyunSls {
	r.Config.Project = project

	return r
}
func (r *aliyunSls) Store(store string) *aliyunSls {
	r.Config.Store = store

	return r
}
func (r *aliyunSls) Topic(topic string) *aliyunSls {
	r.Config.Topic = topic

	return r
}
func (r *aliyunSls) Query(query string) *aliyunSls {
	r.Config.Query = query

	return r
}
func (r *aliyunSls) From(from *gtime.Time) *aliyunSls {
	r.Config.From = from.Timestamp()

	return r
}
func (r *aliyunSls) To(to *gtime.Time) *aliyunSls {
	r.Config.To = to.Timestamp()

	return r
}
func (r *aliyunSls) PageSize(pageSize int64) *aliyunSls {
	r.Config.PageSize = pageSize

	return r
}
func (r *aliyunSls) Reverse(reverse bool) *aliyunSls {
	r.Config.Reverse = reverse

	return r
}

func (r *aliyunSls) NewClient() *aliyunSls {
	r.client = sls.CreateNormalInterface(r.Endpoint(), r.AliyunConfig.AccessId, r.AliyunConfig.AccessKey, r.AliyunConfig.SecurityToken)

	return r
}
func (r *aliyunSls) GetProducer() *producer.Producer {
	r.producersLock.Lock()
	defer r.producersLock.Unlock()

	if p, ok := r.producers[r.slug]; ok {
		return p
	}

	producerConfig := producer.GetDefaultProducerConfig()
	producerConfig.AccessKeyID = r.AliyunConfig.AccessId
	producerConfig.AccessKeySecret = r.AliyunConfig.AccessKey
	producerConfig.Endpoint = r.Endpoint()

	p := producer.InitProducer(producerConfig)
	p.Start()

	r.producers[r.slug] = p
	return p
}
func (r *aliyunSls) Endpoint() string {
	return fmt.Sprintf(aliyunSlsEndpoint, r.AliyunConfig.Region)
}
func (r *aliyunSls) WebLink(query string) string {
	return fmt.Sprintf(aliyunSlsLink, r.Config.Project, r.Config.Store, gbase64.EncodeString(query), r.Config.From, r.Config.To)
}

func (r *aliyunSls) List(pageSize, offset int64) (*sls.GetLogLinesResponse, error) {
	return r.client.GetLogLines(r.Config.Project, r.Config.Store, r.Config.Topic, r.Config.From, r.Config.To, r.Config.Query, pageSize, offset, r.Config.Reverse)
}
func (r *aliyunSls) Page(page int64) (*sls.GetLogLinesResponse, error) {
	offset := (page - 1) * r.Config.PageSize
	return r.List(r.Config.PageSize, offset)
}
func (r *aliyunSls) Show() (*sls.GetLogLinesResponse, error) {
	return r.List(1, 0)
}
func (r *aliyunSls) ListRaw(pageSize, offset int64) []string {
	result, err := r.List(pageSize, offset)
	if err != nil {
		panic(err)
	}

	var items []string

	for _, line := range result.Lines {
		bytes, err := line.MarshalJSON()
		if err != nil {
			panic(err)
		}

		items = append(items, gjson.New(bytes).Get("raw").String())
	}

	return items
}
func (r *aliyunSls) PageRaw(page int64) []string {
	result, err := r.Page(page)
	if err != nil {
		panic(err)
	}

	var items []string

	for _, line := range result.Lines {
		bytes, err := line.MarshalJSON()
		if err != nil {
			panic(err)
		}

		items = append(items, gjson.New(bytes).Get("raw").String())
	}

	return items
}
func (r *aliyunSls) PageRawCallback(page int64, callback func(raw string)) {
	result, err := r.Page(page)
	if err != nil {
		panic(err)
	}

	for _, line := range result.Lines {
		bytes, err := line.MarshalJSON()
		if err != nil {
			panic(err)
		}

		callback(gjson.New(bytes).Get("raw").String())
	}
}
func (r *aliyunSls) ShowRaw() string {
	result, err := r.Show()
	if err != nil {
		panic(err)
	}

	if len(result.Lines) > 0 {
		line := result.Lines[0]
		bytes, err := line.MarshalJSON()
		if err != nil {
			panic(err)
		}

		return gjson.New(bytes).Get("raw").String()
	}

	return ""
}
func (r *aliyunSls) PutTo(data g.Map, p *producer.Producer) {
	var contents []*sls.LogContent
	for k, v := range data {
		contents = append(contents, &sls.LogContent{
			Key:   proto.String(k),
			Value: proto.String(gconv.String(v)),
		})
	}

	slsLog := &sls.Log{
		Time:     proto.Uint32(uint32(gtime.Timestamp())),
		Contents: contents,
	}

	err := p.SendLog(r.Config.Project, r.Config.Store, r.Config.Topic, "source", slsLog)
	if err != nil {
		panic(err)
	}
}
func (r *aliyunSls) Put(data g.Map) {
	p := r.GetProducer()

	r.PutTo(data, p)
}
